package info.fluxprojects.euler.util;

import java.io.File;
import java.io.FilenameFilter;
import java.io.IOException;
import java.net.URL;
import java.util.ArrayList;
import java.util.Enumeration;
import java.util.List;
import java.util.logging.Level;
import java.util.logging.Logger;

/**
 * Copyrights EnergyICT
 * Date: 11/29/12
 * Time: 12:57 AM
 */
public class ProjectEuler {

    private static final Logger LOGGER = Logger.getLogger(ProjectEuler.class.getName());
    public static final String PACKAGE_NAME = "info.fluxprojects.euler.problems";

    public static void main(String[] args) {
        final List<Class<EulerProblem>> eulerClasses = getEulerClasses();
        for (final Class<EulerProblem> eulerClass : eulerClasses) {
            try {
                final EulerProblem eulerProblem = eulerClass.newInstance();
                final Number solution = eulerProblem.getSolution();
                LOGGER.log(Level.INFO, eulerClass.getSimpleName() + ": " + solution);
            } catch (InstantiationException e) {
                LOGGER.log(Level.WARNING, "Unable to get solution from class [" + eulerClass + "]: " + e.getMessage(), e);
            } catch (IllegalAccessException e) {
                LOGGER.log(Level.WARNING, "Unable to get solution from class [" + eulerClass + "]: " + e.getMessage(), e);
            }
        }

    }

    /**
     * @return a list of EulerProblem classes
     */
    private static final List<Class<EulerProblem>> getEulerClasses() {
        final List<Class<EulerProblem>> eulerClasses = new ArrayList<Class<EulerProblem>>();

        final ClassLoader classLoader = Thread.currentThread().getContextClassLoader();
        try {
            final String packageFolder = PACKAGE_NAME.replaceAll("\\.", "/");
            final Enumeration<URL> classUrls = classLoader.getResources(packageFolder);
            while (classUrls.hasMoreElements()) {
                final URL url = classUrls.nextElement();
                final File file = new File(url.getFile());
                if (file.exists()) {
                    final String[] classFiles = file.list(new FilenameFilter() {
                        @Override
                        public boolean accept(final File dir, final String name) {
                            final File fileToCheck = new File(dir.getAbsolutePath() + "/" + name);
                            return name.endsWith(".class") && !fileToCheck.isDirectory()
                                    && fileToCheck.isFile() && fileToCheck.exists();
                        }
                    });

                    for (final String classFileName : classFiles) {
                        final Class<EulerProblem> eulerClass = getEulerClassFromName(classFileName);
                        if (eulerClass != null) {
                            eulerClasses.add(eulerClass);
                        }
                    }

                } else {
                    LOGGER.log(Level.WARNING, "Unable to get the Euler classes: Path does not exist [" + file + "]!");
                }
            }

        } catch (IOException e) {
            LOGGER.log(Level.WARNING, "Unable to get the Euler classes: " + e.getMessage(), e);
        }

        return eulerClasses;
    }

    /**
     * @param classFileName The className to look for (ex. Problem001.class)
     * @return The real class or 'null' if not found
     */
    private static final Class<EulerProblem> getEulerClassFromName(final String classFileName) {

        try {
            final int extensionLength = ".class".length();
            final String javaClassName = classFileName.substring(0, classFileName.length() - extensionLength);
            final Class<?> aClass = Class.forName(PACKAGE_NAME + "." + javaClassName);
            if (EulerProblem.class.isAssignableFrom(aClass)) {
                return (Class<EulerProblem>) aClass;
            }
        } catch (ClassNotFoundException e) {
            LOGGER.log(Level.WARNING, "Unable to get the Euler class [" + classFileName + "]: " + e.getMessage(), e);
        }

        return null;
    }

}
